#ifndef TL_ANIMATION_PLAYER_H
#define TL_ANIMATION_PLAYER_H

#include <unordered_map>
#include <time.h>
#include <memory>
#include <mgapiall.h>

namespace tl
{
class observer
{
};

class frame_number
{
public:
   frame_number (void) :
      m_frame (0),
      m_frameCount (1),
      m_isLooped (true)
   {
   }

   int Get (void) const
   {
      return m_frame;
   }

   int Set (int value)
   {
      if (m_isLooped)
      {
         if (value >= 0)
            m_frame = value % m_frameCount;
         else
            m_frame = (value % m_frameCount + m_frameCount) % m_frameCount;
      }
      else
         m_frame = std::max (0, std::min (m_frameCount - 1, value));

      return m_frame;
   }

   int GetFrameCount (void) const
   {
      return m_frameCount;
   }

   void SetFrameCount (int value)
   {
      if (value > 0)
      {
         m_frameCount = value;

         if (m_frame >= m_frameCount)
            m_frame = m_frameCount - 1;
      }
   }

   int SetToNextFrame ()
   {
      if (m_isLooped)
         m_frame = (m_frame + 1) % m_frameCount;
      else
      {
         if (m_frame < m_frameCount - 1)
            ++m_frame;
      }

      return m_frame;
   }

   int SetToPreviousFrame ()
   {
      if (m_isLooped)
         m_frame = (m_frame + m_frameCount - 1) % m_frameCount;
      else
      {
         if (m_frame > 0)
            --m_frame;
      }

      return m_frame;
   }

   int SetToFirstFrame (void)
   {
      m_frame = 0;
      return m_frame;
   }

   int SetToLastFrame (void)
   {
      m_frame = m_frameCount - 1;
      return m_frame;
   }

   int ChangeBy (int value)
   {
      Set (m_frame + value);
      return m_frame;
   }
private:
   int m_frame;
   int m_frameCount;
   bool m_isLooped;
};

class timer
{
public:
   timer (void) :
      m_startTime (0),
      m_time (0),
      m_isStarted (false)
   {
   }

   double GetTime (void) const
   {
      clock_t time = m_isStarted ? m_time + clock () - m_startTime : m_time;
      return static_cast<double>(time) / CLOCKS_PER_SEC;
   }

   bool IsStarted (void) const
   {
      return m_isStarted;
   }

   void Start (void)
   {
      if (!m_isStarted)
      {
         m_startTime = clock ();
         m_isStarted = true;
      }
   }

   void Pause (void)
   {
      if (m_isStarted)
      {
         m_time += clock () - m_startTime;
         m_isStarted = false;
      }
   }

   void Reset (void)
   {
      m_isStarted = false;
      m_time = 0;
   }
private:
   clock_t m_startTime;
   clock_t m_time;
   bool m_isStarted;
};

class animation_player
{
public:
   class post_update_frame_functor
   {
   public:
      virtual void operator () (const animation_player*) = 0;
      virtual ~post_update_frame_functor (void) {}
   };

   animation_player (mgrec* db) :
      m_db (db),
      m_timerHandle (NULL),
      m_frameTime (0.0)
   {
   }

   ~animation_player (void)
   {
      RestoreDatabase ();
   }

   const std::string& GetAnimationPath (void) const
   {
      return m_animationPath;
   }

   const frame_number& GetFrame (void) const
   {
      return m_frame;
   }

   void GoToFrame (int value)
   {
      if (m_frame.Get () != value)
      {
         m_frame.Set (value);
         UpdateXforms ();
      }
   }

   void SetPostUpdateFrameFunctor (std::tr1::shared_ptr<post_update_frame_functor> spFunctor)
   {
      m_spPostUpdateFrameFunctor = spFunctor;
   }

   bool IsPlaying (void) const
   {
      return m_timer.IsStarted ();
   }

   bool LoadBVHFile (const char* pFilename);
   void Play (void);

   void Pause (void)
   {
      m_timer.Pause ();
   }

   void Stop (void)
   {
      m_timer.Reset ();

      int orginalframe = m_frame.Get ();
      if (orginalframe != m_frame.SetToFirstFrame ())
         UpdateXforms ();
   }

   void GoToPreviousFrame (void)
   {
      int orginalframe = m_frame.Get ();
      if (orginalframe != m_frame.SetToPreviousFrame ())
         UpdateXforms ();
   }

   void GoToNextFrame (void)
   {
      int orginalframe = m_frame.Get ();
      if (orginalframe != m_frame.SetToNextFrame ())
         UpdateXforms ();
   }
private:
   mgrec* m_db;
   std::string m_animationPath;
   mgtimer m_timerHandle;
   frame_number m_frame;
   double m_frameTime;
   timer m_timer;
   frame_number m_startFrame;
   std::vector<mgrec*> m_xforms;
   std::tr1::unordered_map<mgrec*,mgcode> m_xformToCode;
   std::vector<double> m_values;
   std::tr1::shared_ptr<post_update_frame_functor> m_spPostUpdateFrameFunctor;

   static mgbool UpdateFrame (mgtimer timerId, void* userData);

   void UpdateXforms (void);
   void RestoreDatabase (void);
   void Reset (void);
};
} // end namespace tl

#endif